﻿using System;
using System.IO;
using System.Windows;
using BitMiracle.LibTiff.Classic;
using FirstFloor.Documents;
using FirstFloor.Documents.Imaging;

namespace FirstFloor.Documents.IO.LibTiff
{
    /// <summary>
    /// A package reader for TIFF images.
    /// </summary>
    public class TiffPackageReader
        : MemoryPackageReader
    {
        private static readonly string TiffImageUri = string.Format("image{0}", ContentType.FileExtensionTiff);

        private Tiff image;
        private Stream stream;
        private bool disposed;
        private int pageCount = 0;
        private BitmapEncoder encoder;

        /// <summary>
        /// Initializes a new instance of the <see cref="TiffPackageReader"/> class.
        /// </summary>
        /// <param name="resourceUri">The resource URI.</param>
        public TiffPackageReader(Uri resourceUri)
        {
            if (resourceUri == null) {
                throw new ArgumentNullException("resourceUri");
            }
            var streamInfo = Application.GetResourceStream(resourceUri);
            if (streamInfo == null) {
                throw new ArgumentException(string.Format(Resources.ResourceNotFound, resourceUri));
            }

            Initialize(streamInfo.Stream);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TiffPackageReader"/> class.
        /// </summary>
        /// <param name="stream">The stream.</param>
        public TiffPackageReader(Stream stream)
        {
            Initialize(stream);
        }

        private void Initialize(Stream stream)
        {
            stream.VerifyReadAndSeek();
            this.stream = stream;
            this.stream.Seek(0, SeekOrigin.Begin);

            this.image = Tiff.ClientOpen("tiff", "r", stream, new TiffStream());
            if (this.image == null) {
                throw new ArgumentException("Stream does not contain a valid TIFF image");
            }

            // create the bitmap encoder
            this.encoder = new BitmapEncoder();

            // ensure the TIFF decoder is registered
            ImageDecoders.RegisterImageDecoder(ContentType.TypeImageTiff, new TiffDecoder());
        }

        /// <summary>
        /// Gets the number of pages.
        /// </summary>
        /// <value>The page count.</value>
        public override int PageCount
        {
            get
            {
                if (this.pageCount == 0) {
                    this.pageCount = this.image.NumberOfDirectories();
                }
                return this.pageCount;
            }
        }

        /// <summary>
        /// Gets the XAML of specified page.
        /// </summary>
        /// <param name="pageNumber">The page number.</param>
        /// <returns></returns>
        public override string GetPageXaml(int pageNumber)
        {
            var directoryNumber = (short)(pageNumber - 1);
            if (!this.image.SetDirectory(directoryNumber)) {
                return null;    // not found
            }

            var xaml = @"
<doc:FixedPage Width='{0}' Height='{1}' xmlns:doc='http://schemas.firstfloorsoftware.com/documenttoolkit'>
  <Path Data='M0,0 {0},0 {0},{1} 0,{1}'>
    <Path.Fill>
      <ImageBrush ImageSource='{2}#{3}' />
    </Path.Fill>
  </Path>
</doc:FixedPage>";

            var imageSize = CalculateImageSize();
            var width = (int)imageSize.Width;
            var height = (int)imageSize.Height;

            // the image file directory is encoded in the uri (supported by IImageDecoder)
            return string.Format(xaml, width, height, TiffImageUri, directoryNumber);
        }

        /// <summary>
        /// Gets the part identified with given uri.
        /// </summary>
        /// <param name="partUri">The part URI.</param>
        /// <returns></returns>
        public override Stream GetPart(string partUri)
        {
            if (partUri != null && UriHelper.GetUriStringWithoutFragment(partUri) == TiffImageUri) {
                // return copy of TIFF image stream as-is, TIFF decoder will select correct image file directory based on url encoded image frame
                this.stream.Seek(0, SeekOrigin.Begin);

                return this.stream.ToMemoryStream();
            }
            // not found
            return null;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            if (!this.disposed) {
                this.disposed = true;
                this.image.Dispose();
                this.stream.Dispose();
            }
        }

        private Size CalculateImageSize()
        {
            var frameSize = this.image.GetFrameSize();
            var xResolutionField = this.image.GetField(TiffTag.XRESOLUTION);
            var yResolutionField = this.image.GetField(TiffTag.YRESOLUTION);

            var xResolution = xResolutionField != null && xResolutionField.Length > 0 ? new double?(xResolutionField[0].ToDouble()) : null;
            var yResolution = yResolutionField != null && yResolutionField.Length > 0 ? new double?(yResolutionField[0].ToDouble()) : null;

            double widthMultiplicator = 1.0;
            double heightMultiplicator = 1.0;

            // DPI resolution can be different for x and y.
            if (xResolution != null) {
                if (yResolution == null) {
                    yResolution = xResolution;
                }

                if (xResolution > yResolution) {
                    widthMultiplicator = yResolution.Value / xResolution.Value;
                }
                else if (yResolution > xResolution) {
                    heightMultiplicator = xResolution.Value / yResolution.Value;
                }
            }

            return new Size(frameSize.Width * widthMultiplicator, frameSize.Height * heightMultiplicator);
        }

        /// <summary>
        /// Determines whether the specified stream contains a TIFF image.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>
        /// 	<c>true</c> if specified stream contains a TIFF image; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsTiffImage(Stream stream)
        {
            stream.VerifyReadAndSeek();
            stream.Seek(0, SeekOrigin.Begin);

            if (stream.IsMatch(0x49, 0x49, 0x2a, 0x0)) {
                // little endian
                return true;
            }
            stream.Seek(0, SeekOrigin.Begin);
            if (stream.IsMatch(0x4d, 0x4d, 0x0, 0x2a)) {
                // big endian
                return true;
            }

            return false;
        }
    }
}
